﻿using Autodesk.Revit.DB;
using CoreDB.Models;
using System;
using System.Collections.Generic;
using System.Linq;

namespace CoreDB.Extensions
{
    /// <summary>
    /// 与几何线段相关的扩展方法。
    /// 命名规则: 
    /// 1. 在能清晰表达方法意思的前提下尽可能的简单。该类中的方法是针对线段的，调用者已经知道调用方的类型是
    ///    几何线段，因此方法名中可以省略Line字样。
    /// </summary>
    public static class LineExtension
    {
        private const double defaultEps = 1e-3;

        #region 平行，垂直判断
        /// <summary>
        /// 判断线是否竖直
        /// 既可以向上，也可以向下
        /// </summary>
        /// <param name="c"></param>
        /// <param name="eps"></param>
        /// <returns></returns>
        public static bool IsVertical(this Line line, double eps = defaultEps)
        {
            return line.Direction.IsParallel(XYZ.BasisZ, eps, false);
        }
        /// <summary>
        /// 判断两个线段是否互相垂直
        /// </summary>
        /// <param name="line"></param>
        /// <param name="another"></param>
        /// <param name="eps"></param>
        /// <returns></returns>
        public static bool IsVerticalTo(this Line line, Line another, double eps = defaultEps)
        {
            return line.Direction.IsVerticalTo(another.Direction, eps);
        }
        /// <summary>
        /// 判断指定线段是否与另一条线段平行。
        /// </summary>
        /// <param name="line">指定线段</param>
        /// <param name="another">另一条线段</param>
        /// <param name="ignoreOpp">True:只支持相同方向</param>
        /// <param name="eps">容差</param>
        /// <returns>是否平行</returns>
        public static bool IsParallel(this Line line, Line another, bool ignoreOpp = false, double eps = defaultEps)
        {
            return line.Direction.IsParallel(another.Direction, eps, ignoreOpp);
        }
        #endregion

        #region Split 分割线段
        /// <summary>
        /// 传入直线和一个点，将线段且成两段
        /// 如果点不能垂直投影，则返回原有线段
        /// 注：不强制要求点必须在线段上，但是强制必须可以垂直投影到线段上，否则返回空
        /// </summary>
        /// <param name="line"></param>
        /// <param name="point"></param>
        /// <returns></returns>
        public static List<Line> Split(this Line line, XYZ point)
        {
            List<Line> resultLines = new List<Line>();
            if (point.CanVerticalProject(line) && line.IsBound)
            {
                XYZ projectPt = point.ProjectM(line).projectPt;
                if (line.Start().CanMakeBound(projectPt))
                    resultLines.Add(Line.CreateBound(line.Start(), projectPt));
                if (projectPt.CanMakeBound(line.End()))
                    resultLines.Add(Line.CreateBound(projectPt, line.End()));
            }
            else
                resultLines.Add(line);
            return resultLines;
        }
        public static List<Line> Split(this Line line, List<XYZ> points)
        {
            List<Line> resultLines = new List<Line>();
            if (!line.IsBound)
            {
                resultLines.Add(line);
                return resultLines;
            }
            List<XYZ> projPts = new List<XYZ>();
            foreach (var point in points)
            {
                if (point.CanVerticalProject(line))
                {
                    XYZ projPt = point.ProjectM(line).projectPt;
                    projPts.Add(projPt);
                }
            }
            var orderedPts = projPts.OrderBy(x => x.DotProduct(line.Direction));
            for (int i = 0; i <= orderedPts.Count(); i++)
            {
                if (i == 0)
                {
                    if (line.Start().CanMakeBound(orderedPts.ElementAtOrDefault(i)))
                        resultLines.Add(Line.CreateBound(line.Start(), orderedPts.ElementAtOrDefault(i)));
                }
                else if (i == orderedPts.Count())
                {
                    if (orderedPts.ElementAtOrDefault(i).CanMakeBound(line.End()))
                        resultLines.Add(Line.CreateBound(orderedPts.ElementAtOrDefault(i), line.End()));
                }
                else if (orderedPts.ElementAtOrDefault(i - 1).CanMakeBound(orderedPts.ElementAtOrDefault(i)))
                    resultLines.Add(Line.CreateBound(orderedPts.ElementAtOrDefault(i - 1), orderedPts.ElementAtOrDefault(i)));
            }
            return resultLines;
        }

        /// <summary>
        /// 线段等分
        /// </summary>
        /// <param name="line"></param>
        /// <param name="count"></param>
        /// <param name="includeEnds"></param>
        /// <returns></returns>
        public static List<XYZ> Split(this Line line, int count, bool includeEnds = false)
        {
            if (count <= 0 || !line.IsBound)
                return null;
            var points = new List<XYZ>();
            double step = 1.0 / count;
            for (int i = includeEnds ? 0 : 1; i < (includeEnds ? count + 1 : count); i++)
                points.Add(line.Evaluate(i * step, true));
            return points;
        }
        public static List<Line> Split(this Line line, int count)
        {
            var pts = line.Split(count, true);
            var results = new List<Line>();
            if (pts == null)
                return null;
            for (int i = 0; i < pts.Count - 1; i++)
                if (pts[i].CanMakeBound(pts[i + 1]))
                    results.Add(Line.CreateBound(pts[i], pts[i + 1]));
            return results;
        }
        #endregion

        #region 基本方法
        /// <summary>
        /// 得到两条平行线间的距离，反正就是取最近的距离
        /// 注：本方法不验证平行，如果不平行，结果可能不正确
        /// </summary>
        /// <param name="line"></param>
        /// <param name="another"></param>
        /// <returns></returns>
        public static double DistanceTo(this Line line1, Line line2, double eps = defaultEps)
        {
            double l1dis1 = line1.Start().ProjectM(line2, true).projectDistance;
            double l1dis2 = line1.End().ProjectM(line2, true).projectDistance;
            double l2dis1 = line2.Start().ProjectM(line1, true).projectDistance;
            double l2dis2 = line2.End().ProjectM(line1, true).projectDistance;

            return new List<double>() { l1dis1, l1dis2, l2dis1, l2dis2 }.Min();
        }


        /// <summary>
        /// 将线段拍平
        /// 注意：如果线段是垂直的，则无法拍平，返回 Null
        /// </summary>
        /// <param name="line"></param>
        /// <param name="z"></param>
        /// <returns></returns>
        public static Line Flat(this Line line, double z = 0.0)
        {
            if (line.IsBound)
            {
                XYZ start = line.Start().Flat(z);
                XYZ end = line.End().Flat(z);
                if (start.CanMakeBound(end))
                    return Line.CreateBound(start, end);
            }
            else
            {
                XYZ direction = line.Direction.Flat(0).Normalizex();
                if (!direction.IsZeroLength())
                    return Line.CreateUnbound(line.Origin.Flat(z), direction);
            }
            return null;
        }
        public static Line GetUnBound(this Line line)
        {
            Line l = line.CloneLine();
            l.MakeUnbound();
            return l;
        }
        /// <summary>
        /// 未验证
        /// </summary>
        /// <param name="line"></param>
        /// <returns></returns>
        public static Line Reverse(this Line line)
        {
            return line.CreateReversed() as Line;
        }
        public static Line CloneLine(this Line line)
        {
            return line.Clone() as Line;
        }
        /// <summary>
        /// 判断两个线段或直线是否相同
        /// </summary>
        /// <param name="line1"></param>
        /// <param name="line2"></param>
        /// <param name="ignoreDir">True: 无视方向，只要重叠就认为他们相同，默认只拿方向一致的</param>
        /// <param name="eps"></param>
        /// <returns></returns>
        public static bool IsEqual(this Line line1, Line line2, bool ignoreDir = false, double eps = defaultEps)
        {
            if (!line1.IsBound && !line2.IsBound)// 如果他们都是直线
            {
                // 先判断方向，再判断 origin 与线段的距离是否为零
                if ((line1.Direction.IsEqual(line2.Direction, eps) || (ignoreDir && line1.Direction.IsEqual(line2.Direction.Negate(), eps))) &&
                    line1.Origin.ProjectM(line2, true).projectDistance.IsZero(eps) &&
                    line2.Origin.ProjectM(line1, true).projectDistance.IsZero(eps))
                    return true;
            }
            else if (line1.IsBound && line2.IsBound)// 如果他们都是线段
            {
                if ((line1.Start().IsEqual(line2.Start(), eps) && line1.End().IsEqual(line2.End(), eps)) ||
                    (ignoreDir && line1.Start().IsEqual(line2.End(), eps) && line1.End().IsEqual(line2.Start(), eps)))
                    return true;
            }
            return false;
        }

        /// <summary>
        /// 将指定线段向某个方向偏移一段距离，返回新的线段。
        /// </summary>
        /// <param name="line">指定线段</param>
        /// <param name="direction">偏移方向</param>
        /// <param name="distance">偏移距离</param>
        /// <returns>偏移后的新线段</returns>
        public static Line Offset(this Line line, XYZ direction, double distance)
        {
            if (direction.IsEqual(XYZ.Zero) || !line.IsBound)
                return line;

            return Line.CreateBound(line.Start().Offset(direction, distance), line.End().Offset(direction, distance));
        }




        #endregion


        /// <summary>
        /// 求两个线段之间的投影，容错率很高，如果无法生成则返回null
        /// </summary>
        /// <param name="thisline">投影的主体，默认是线段</param>
        /// <param name="otherLine">投影的客体，必须是线段</param>
        /// <param name="extendLine">True：投影的主体延伸成为直线</param>
        /// <returns></returns>
        public static Line Project(this Line thisline, Line otherLine, bool extendLine = false)
        {
            if (otherLine.IsBound)
            {
                XYZ st = otherLine.Start().ProjectM(thisline, extendLine).projectPt;
                XYZ ed = otherLine.End().ProjectM(thisline, extendLine).projectPt;
                if (st.CanMakeBound(ed))
                {
                    var pts = new List<XYZ>() { st, ed };
                    XYZ newst = pts.MinSource(x => x.DistanceTo(thisline.Start()));
                    XYZ newed = pts.MinSource(x => x.DistanceTo(thisline.End()));
                    return Line.CreateBound(newst, newed);
                }
            }
            return null;
        }
        /// <summary>
        /// 两个线段的差集，可能是0-2根线段
        /// </summary>
        /// <param name="thisLine">差集的主体</param>
        /// <param name="otherLine">差集的客体</param>
        /// <returns></returns>
        public static List<Line> ProjectDiffer(this Line thisLine, Line otherLine)
        {
            List<Line> resultLines = new List<Line>();
            if (thisLine.IsBound && otherLine.IsBound)
            {
                Line projLine = thisLine.Project(otherLine);
                if (projLine != null)
                {
                    var pts = projLine.Vertices();
                    XYZ proj0 = pts.MinSource(x => thisLine.Start().DistanceTo(x));
                    XYZ proj1 = pts.MinSource(x => thisLine.End().DistanceTo(x));
                    if (thisLine.Start().CanMakeBound(proj0))
                        resultLines.Add(Line.CreateBound(thisLine.Start(), proj0));
                    if (proj1.CanMakeBound(thisLine.End()))
                        resultLines.Add(Line.CreateBound(proj1, thisLine.End()));
                }
                else
                    resultLines.Add(thisLine);
            }
            return resultLines;
        }
        /// <summary>
        /// 一个线段与若干线段的求差集
        /// 注：对线段方向没有要求
        /// </summary>
        /// <param name="thisLine">差集的主体</param>
        /// <param name="differLines">差集的客体</param>
        /// <returns></returns>
        public static List<Line> ProjectDiffer(this Line thisLine, IEnumerable<Line> differLines)
        {
            List<Line> resultLines = new List<Line>() { thisLine };
            List<Line> differLineList = differLines.ToList();
            while (differLineList.Count() > 0)
            {
                var tempLines = new List<Line>();
                foreach (var resLine in resultLines)
                {
                    var tempLs = resLine.ProjectDiffer(differLineList.FirstOrDefault());
                    if (tempLs.Count > 0)
                        tempLines.AddRange(tempLs);
                }
                differLineList.RemoveAt(0);
                resultLines = tempLines;
                if (tempLines.Count == 0)
                    break;
            }
            return resultLines;
        }
        /// <summary>
        /// [过时]其实这个方法本身没有问题，只是找到了更好的实现方式
        /// </summary>
        /// <param name="thisLine"></param>
        /// <param name="otherLine"></param>
        /// <returns></returns>
        //public static List<Line> ProjectUnion(this Line thisLine, Line otherLine, double eps = defaultEps)
        //{
        //    // 1 otherLine端点不能垂直投影的点收集起来
        //    List<TempPoint> newPts = new List<TempPoint>();
        //    CollectTempPoint(otherLine.Start());
        //    CollectTempPoint(otherLine.End());

        //    // 2 将端点累加进原有线段
        //    Line cloneLine = thisLine.CloneLine();
        //    List<Line> results = new List<Line>() { cloneLine };
        //    AddEndSidePt(0);
        //    AddEndSidePt(1);
        //    return results;

        //    void AddEndSidePt(int tag)
        //    {
        //        var sideNewPts = newPts.FindAll(x => x.Tag == tag);
        //        if (sideNewPts.Count == 1)
        //            cloneLine = tag == 0 ? Line.CreateBound(sideNewPts.First().Coord, cloneLine.End())
        //                : Line.CreateBound(cloneLine.Start(), sideNewPts.First().Coord);
        //        else if (sideNewPts.Count == 2)
        //            if (sideNewPts.First().Coord.CanMakeBound(sideNewPts.Last().Coord))
        //                results.Add(Line.CreateBound(sideNewPts.First().Coord, sideNewPts.Last().Coord));
        //    }
        //    void CollectTempPoint(XYZ otherPt)
        //    {
        //        if (!otherPt.CanVerticalProject(thisLine, eps))
        //        {
        //            XYZ projPt = otherPt.ProjectM(thisLine, true).projectPt;
        //            double d0 = thisLine.Start().DistanceTo(projPt);
        //            double d1 = thisLine.End().DistanceTo(projPt);
        //            newPts.Add(new TempPoint(projPt) { Tag = d0 < d1 ? 0 : 1 });
        //        }
        //    }
        //}


        /// <summary>
        /// 用点乘原理实现两个线段的投影合并
        /// </summary>
        /// <param name="thisLine"></param>
        /// <param name="otherLine"></param>
        /// <returns></returns>
        public static List<Line> ProjectUnion(this Line thisLine, Line otherLine, double eps = defaultEps)
        {
            List<Line> results = new List<Line>();
            if (!thisLine.IsBound || !otherLine.IsBound)
            {
                results.Add(thisLine.GetUnBound());
                return results;
            }
            XYZ dir = thisLine.Direction;
            TempPoint stTPt = new TempPoint(thisLine.Start()) { Value = thisLine.Start().DotProduct(dir), Tag = 1 };
            TempPoint edTPt = new TempPoint(thisLine.End()) { Value = thisLine.End().DotProduct(dir), Tag = 2 };
            TempPoint other0Pt = new TempPoint(otherLine.Start()) { Value = otherLine.Start().DotProduct(dir), Tag = -1 };
            TempPoint other1Pt = new TempPoint(otherLine.End()) { Value = otherLine.End().DotProduct(dir), Tag = -2 };
            List<TempPoint> tempTpts = new List<TempPoint>() { stTPt, edTPt, other0Pt, other1Pt };
            var orderedTpts = tempTpts.OrderBy(x => x.Value).ToList();
            int stIndex = orderedTpts.FindIndex(x => x.Tag == 1);
            int edIndex = orderedTpts.FindIndex(x => x.Tag == 2);
            if (stIndex > 1)// 返回两条线段
            {
                var otherPts = orderedTpts.FindAll(x => x.Tag < 0);
                if (otherPts.First().Coord.CanMakeBound(otherPts.Last().Coord))
                    results.Add(Line.CreateBound(otherPts.First().Coord, otherPts.Last().Coord));
                results.Add(thisLine.CloneLine());
            }
            else if (edIndex < 2)
            {
                results.Add(thisLine.CloneLine());
                var otherPts = orderedTpts.FindAll(x => x.Tag < 0);
                if (otherPts.First().Coord.CanMakeBound(otherPts.Last().Coord))
                    results.Add(Line.CreateBound(otherPts.First().Coord, otherPts.Last().Coord));
            }
            else
            {
                if (orderedTpts.First().Coord.CanMakeBound(orderedTpts.Last().Coord))
                    results.Add(Line.CreateBound(orderedTpts.First().Coord, orderedTpts.Last().Coord));
            }
            return results;
        }

        /// <summary>
        /// 一个线段与多个线段投影相加的并集
        /// 
        /// value代表在这个方向上的参考值
        /// tag代表线段的序号
        /// IsUsed代表是否起点
        /// </summary>
        /// <param name="thisLine"></param>
        /// <param name="unionLines"></param>
        /// <param name="eps"></param>
        /// <returns></returns>
        public static List<Line> ProjectUnion(this Line thisLine, IEnumerable<Line> unionLines, double eps = defaultEps)
        {
            if (!thisLine.IsBound || !unionLines.All(x => x.IsBound))
                return new List<Line>();

            XYZ dir = thisLine.Direction;
            TempPoint stTPt = new TempPoint(thisLine.Start()) { Value = thisLine.Start().DotProduct(dir), Tag = -1, IsUsed = true };
            TempPoint edTPt = new TempPoint(thisLine.End()) { Value = thisLine.End().DotProduct(dir), Tag = -2, IsUsed = false };
            List<TempPoint> tempTpts = new List<TempPoint>() { stTPt, edTPt };
            var otherLines = unionLines.ToList();
            for (int i = 0; i < otherLines.Count; i++)
            {
                double stValue = otherLines[i].Start().DotProduct(dir);
                double edValue = otherLines[i].End().DotProduct(dir);
                bool b = stValue < edValue;
                tempTpts.Add(new TempPoint(otherLines[i].Start()) { Value = stValue, Tag = i, IsUsed = b });
                tempTpts.Add(new TempPoint(otherLines[i].End()) { Value = edValue, Tag = i, IsUsed = !b });
            }
            var orderedTpts = tempTpts.OrderBy(x => x.Value).ToList();
            int stIndex = orderedTpts.FindIndex(x => x.Tag == -1);
            int edIndex = orderedTpts.FindIndex(x => x.Tag == -2);

            List<int> resultIndexs = new List<int>() { 0, orderedTpts.Count - 1 };
            CollectResultIndexs(true, stIndex);
            CollectResultIndexs(false, edIndex);
            resultIndexs.Sort();
            List<Line> resultLines = new List<Line>();
            for (int i = 0; i < resultIndexs.Count; i += 2)
            {
                XYZ tp0 = orderedTpts[resultIndexs[i]].Coord.ProjectM(thisLine, true, eps).projectPt;
                XYZ tp1 = orderedTpts[resultIndexs[i + 1]].Coord.ProjectM(thisLine, true, eps).projectPt;
                if (tp0.CanMakeBound(tp1))
                    resultLines.Add(Line.CreateBound(tp0, tp1));
            }
            return resultLines;

            void CollectResultIndexs(bool isPositive, int targetIndex)
            {
                List<int> historyIndex = new List<int>();
                List<int> indexs = new List<int>() { isPositive ? 0 : orderedTpts.Count - 1 };
                while (indexs.Count > 0)
                {
                    int thisIndex = indexs.First();
                    if (isPositive ? (thisIndex >= targetIndex) : (thisIndex <= targetIndex))
                        break;
                    TempPoint thisTpt = orderedTpts[thisIndex];
                    int nextIndex = orderedTpts.FindIndex(x => x.Tag == thisTpt.Tag && x.IsUsed != isPositive);
                    if (nextIndex < 0)
                        break;
                    historyIndex.Add(nextIndex);
                    if (isPositive ? (nextIndex >= targetIndex) : (nextIndex <= targetIndex))
                        break;
                    List<TempPoint> tempOrderedTpts;
                    if (isPositive)
                        tempOrderedTpts = orderedTpts.GetRange(thisIndex + 1, nextIndex - thisIndex - 1);
                    else
                    {
                        tempOrderedTpts = orderedTpts.GetRange(nextIndex + 1, thisIndex - nextIndex - 1);
                        tempOrderedTpts.Reverse();
                    }
                    var tags = tempOrderedTpts.FindAll(x => x.IsUsed == isPositive).Select(x => x.Tag).ToList();
                    indexs.RemoveAt(0);
                    foreach (var tag in tags)
                    {
                        int tempIndex = orderedTpts.FindIndex(x => x.Tag == tag && x.IsUsed == isPositive);
                        if (tempIndex > 0 && !indexs.Contains(tempIndex))
                            indexs.Add(tempIndex);
                    }
                    if (indexs.Count == 0)
                    {
                        int lastIndex = isPositive ? historyIndex.Max() : historyIndex.Min();
                        resultIndexs.Add(lastIndex);
                        int newStIndex = isPositive ? lastIndex + 1 : lastIndex - 1;
                        resultIndexs.Add(newStIndex);
                        indexs.Add(newStIndex);
                    }
                }
            }
        }


    }

    public static class LinesExtension
    {
        public static List<Line> Flat(this IEnumerable<Line> lines, double z = 0.0)
        {
            List<Line> results = new List<Line>();
            foreach (var line in lines)
            {
                Line flatLine = line.Flat(z);
                if (flatLine != null)
                    results.Add(flatLine);
            }
            return results;
        }




    }
}
